/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Haystack Software Inc. All rights reserved.
 *  Licensed under the PolyForm Strict License 1.0.0. See License.txt in the project root for
 *  license information.
 *--------------------------------------------------------------------------------------------*/

/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See code-license.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

;(function () {
  interface IMonacoEnvironment {
    baseUrl?: string
    createTrustedTypesPolicy<Options extends TrustedTypePolicyOptions>(
      policyName: string,
      policyOptions?: Options
    ):
      | undefined
      | Pick<
          TrustedTypePolicy<Options>,
          "name" | Extract<keyof Options, keyof TrustedTypePolicyOptions>
        >
  }
  const monacoEnvironment: IMonacoEnvironment | undefined = (globalThis as any)
    .MonacoEnvironment
  const monacoBaseUrl =
    monacoEnvironment && monacoEnvironment.baseUrl
      ? monacoEnvironment.baseUrl
      : "../../../"

  function createTrustedTypesPolicy<Options extends TrustedTypePolicyOptions>(
    policyName: string,
    policyOptions?: Options
  ):
    | undefined
    | Pick<
        TrustedTypePolicy<Options>,
        "name" | Extract<keyof Options, keyof TrustedTypePolicyOptions>
      > {
    if (monacoEnvironment?.createTrustedTypesPolicy) {
      try {
        return monacoEnvironment.createTrustedTypesPolicy(
          policyName,
          policyOptions
        )
      } catch (err) {
        console.warn(err)
        return undefined
      }
    }

    try {
      return self.trustedTypes?.createPolicy(policyName, policyOptions)
    } catch (err) {
      console.warn(err)
      return undefined
    }
  }

  const trustedTypesPolicy = createTrustedTypesPolicy("amdLoader", {
    createScriptURL: (value) => value,
    createScript: (_, ...args: string[]) => {
      // workaround a chrome issue not allowing to create new functions
      // see https://github.com/w3c/webappsec-trusted-types/wiki/Trusted-Types-for-function-constructor
      const fnArgs = args.slice(0, -1).join(",")
      const fnBody = args.pop()!.toString()
      // Do not add a new line to fnBody, as this will confuse source maps.
      const body = `(function anonymous(${fnArgs}) { ${fnBody}\n})`
      return body
    },
  })

  function canUseEval(): boolean {
    try {
      const func = trustedTypesPolicy
        ? globalThis.eval(<any>trustedTypesPolicy.createScript("", "true")) // CodeQL [SM01632] fetch + eval is used on the web worker instead of importScripts if possible because importScripts is synchronous and we observed deadlocks on Safari
        : new Function("true") // CodeQL [SM01632] fetch + eval is used on the web worker instead of importScripts if possible because importScripts is synchronous and we observed deadlocks on Safari
      func.call(globalThis)
      return true
    } catch (err) {
      return false
    }
  }

  function loadAMDLoader() {
    return new Promise<void>((resolve, reject) => {
      if (
        typeof (<any>globalThis).define === "function" &&
        (<any>globalThis).define.amd
      ) {
        return resolve()
      }
      const loaderSrc: string | TrustedScriptURL =
        monacoBaseUrl + "vs/loader.js"

      const isCrossOrigin =
        /^((http:)|(https:)|(file:))/.test(loaderSrc) &&
        loaderSrc.substring(0, globalThis.origin.length) !== globalThis.origin
      if (!isCrossOrigin && canUseEval()) {
        // use `fetch` if possible because `importScripts`
        // is synchronous and can lead to deadlocks on Safari
        fetch(loaderSrc)
          .then((response) => {
            if (response.status !== 200) {
              throw new Error(response.statusText)
            }
            return response.text()
          })
          .then((text) => {
            text = `${text}\n//# sourceURL=${loaderSrc}`
            const func = trustedTypesPolicy
              ? globalThis.eval(
                  trustedTypesPolicy.createScript("", text) as unknown as string
                ) // CodeQL [SM01632] fetch + eval is used on the web worker instead of importScripts if possible because importScripts is synchronous and we observed deadlocks on Safari
              : new Function(text) // CodeQL [SM01632] fetch + eval is used on the web worker instead of importScripts if possible because importScripts is synchronous and we observed deadlocks on Safari
            func.call(globalThis)
            resolve()
          })
          .then(undefined, reject)
        return
      }

      if (trustedTypesPolicy) {
        importScripts(
          trustedTypesPolicy.createScriptURL(loaderSrc) as unknown as string
        )
      } else {
        importScripts(loaderSrc as string)
      }
      resolve()
    })
  }

  function configureAMDLoader() {
    require.config({
      baseUrl: monacoBaseUrl,
      catchError: true,
      trustedTypesPolicy,
      amdModulesPattern: /^vs\//,
    })
  }

  function loadCode(moduleId: string) {
    loadAMDLoader().then(() => {
      configureAMDLoader()
      require([moduleId], function (ws) {
        setTimeout(function () {
          const messageHandler = ws.create(
            (msg: any, transfer?: Transferable[]) => {
              ;(<any>globalThis).postMessage(msg, transfer)
            },
            null
          )

          globalThis.onmessage = (e: MessageEvent) =>
            messageHandler.onmessage(e.data, e.ports)
          while (beforeReadyMessages.length > 0) {
            const e = beforeReadyMessages.shift()!
            messageHandler.onmessage(e.data, e.ports)
          }
        }, 0)
      })
    })
  }

  // If the loader is already defined, configure it immediately
  // This helps in the bundled case, where we must load nls files
  // and they need a correct baseUrl to be loaded.
  if (
    typeof (<any>globalThis).define === "function" &&
    (<any>globalThis).define.amd
  ) {
    configureAMDLoader()
  }

  let isFirstMessage = true
  const beforeReadyMessages: MessageEvent[] = []
  globalThis.onmessage = (message: MessageEvent) => {
    if (!isFirstMessage) {
      beforeReadyMessages.push(message)
      return
    }

    isFirstMessage = false
    loadCode(message.data)
  }
})()
